﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Collections;
using System.Data.Linq;
using System.Reflection;

namespace Nexterday.Linq.Autocompiler.L2S
{
    public class CompilableQuery<T> : IQueryable<T>, IOrderedQueryable<T>, IOrderedQueryable
    {
        IQueryProvider _provider;
        IQueryable<T> _inner;
        internal IQueryable<T> InnerQuery { get { return _inner; } }			// Original query, that we're wrapping

        public CompilableQuery(IQueryable<T> inner, CompilationParameters parameters)
        {
            _inner = inner;
            _provider = new CompilableQueryProvider<T>(this, parameters);
        }

        Expression IQueryable.Expression { get { return _inner.Expression; } }
        Type IQueryable.ElementType { get { return typeof(T); } }
        IQueryProvider IQueryable.Provider { get { return _provider; } }
        public IEnumerator<T> GetEnumerator()
        {
            IEnumerable<T> val;
            if (typeof(IOrderedQueryable<T>).IsAssignableFrom(_inner.Expression.Type))
            {
                val = _provider.Execute<IOrderedQueryable<T>>(_inner.Expression);
            }
            else
            {
                val = _provider.Execute<IQueryable<T>>(_inner.Expression);
            }
            return val.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator() { return _inner.GetEnumerator(); }
        public override string ToString() { return _inner.ToString(); }
    }

    class CompilableQueryProvider<T> : IQueryProvider
    {
        CompilableQuery<T> _query;
        CompilationParameters _compilationParameters;
        ICompiledQueryCache _cache;

        internal CompilableQueryProvider(CompilableQuery<T> query, CompilationParameters parameters)
        {
            _query = query;
            _compilationParameters = parameters;
            _cache=(parameters.CacheFactoryDelegate ?? (()=>SimpleCompiledQueryCache.Instance)).Invoke();
        }

        IQueryable<TElement> IQueryProvider.CreateQuery<TElement>(Expression expression)
        {
            return new CompilableQuery<TElement>(_query.InnerQuery.Provider.CreateQuery<TElement>(expression), _compilationParameters);
        }

       
        TResult IQueryProvider.Execute<TResult>(Expression expression)
        {
            
            expression = ExpressionFlattener.Flatten(expression);
            
            var result = ParametrizingExpressionVisitor.Process(expression,_compilationParameters);

            string cacheKey = ExpressionPrinter.ToString(result.Expression);

            var query = _cache.GetOrAdd<TResult>(cacheKey, (a) => {
                
                var lambda = Expression.Lambda(result.Expression, new ParameterExpression[] { 
                            result.DataContextParameter, result.ParameterContainerParameter }
                ) as Expression<Func<DataContext, object[], TResult>>;
                
                return CompiledQuery.Compile<DataContext, object[], TResult>(lambda);
            });

            var compiledQuery = query as Func<DataContext, object[], TResult>;

            return compiledQuery.Invoke(result.DataContext, result.ParameterValues.ToArray());
        }

        //Fallback
        object IQueryProvider.Execute(Expression expression)
        {
            return _query.InnerQuery.Provider.Execute(expression);
        }

        IQueryable IQueryProvider.CreateQuery(Expression expression)
        {
            return _query.InnerQuery.Provider.CreateQuery(expression);
        }
    }
}
